package fussbudget.abstracts;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.TransferHandler;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;

import fussbudget.PaymentEntity;
import fussbudget.gui.components.BasicPopupMenu;
import fussbudget.gui.components.GenericTransferable;
import fussbudget.gui.components.HasPopupMenu;

public class AbstractTreeTableWithPopup extends JXTreeTable implements HasPopupMenu {
	public BasicPopupMenu				popup;
	private AbstractDraggableElement	root;

	public AbstractTreeTableWithPopup() {
		super();
	}

	public AbstractTreeTableWithPopup(DefaultTreeTableModel model) {
		super(model);
		this.root = (AbstractDraggableElement) model.getRoot();
		this.setDropTarget(new TreeTableDropTarget());
		TransferHandler handler = new MyCustomTransferHandler();
		this.setTransferHandler(handler);
		this.setDragEnabled(true);
	}

	public BasicPopupMenu getPopup() {
		return popup;
	}

	public void setPopup(BasicPopupMenu newPopup) {
		this.popup = newPopup;
	}

	public void setRoot(AbstractDraggableElement newRoot) {
		System.out.println("Root changed to: " + newRoot.toString());
		this.root = newRoot;
	}

	public AbstractDraggableElement getRoot() {
		return this.root;
	}

	@Override
	public void setupPopup() {
	}

	public class TableHeaderWithPopup extends JTableHeader implements HasPopupMenu {
		// not currently in use...
		public BasicPopupMenu	popup;

		public TableHeaderWithPopup() {
			super();
		}

		public TableHeaderWithPopup(TableColumnModel cm) {
			super(cm);
		}

		public TableCellRenderer getDefaultRenderer() {
			System.out.println("getDefaultRenderer");
			return super.getDefaultRenderer();
		}

		@Override
		public void setupPopup() {
		}

		public BasicPopupMenu getPopup() {
			return popup;
		}

		public void setPopup(BasicPopupMenu newPopup) {
			this.popup = newPopup;
		}
	}

	public class TreeTableDropTarget extends DropTarget {
		public TreeTableDropTarget() {
			super();
		}

		public void dragExit(DropTargetEvent dte) {
			System.out.println(dte.toString());
			super.dragExit(dte);
		}

		public void drop(DropTargetDropEvent dtde) {
			System.out.println(dtde.toString());
			TreeTableDropTarget source = (TreeTableDropTarget) dtde.getSource();
			AbstractTreeTableWithPopup table = (AbstractTreeTableWithPopup) source.getComponent();
			Point location = dtde.getLocation();
			TreePath dragPath = table.getPathForLocation(location.x, location.y);
			int row = table.getRowForPath(dragPath);
			AbstractDraggableElement targetNode;
			AbstractDraggableElement moveNode;

			try {
				Transferable transfer = (Transferable) dtde.getTransferable();
				Object flavors = transfer.getTransferDataFlavors()[0];
				TreePath draggedPath = (TreePath) transfer.getTransferData((DataFlavor) flavors);
				moveNode = ((AbstractDraggableElement) draggedPath.getLastPathComponent());
				
				if (row != -1) {
					TreePath dropPath = table.getPathForRow(row);
					targetNode = ((AbstractDraggableElement) dropPath.getLastPathComponent());
				} else {
					targetNode = ((AbstractDraggableElement) draggedPath.getPathComponent(0));
				}
				
				if (moveNode != targetNode && targetNode.isGroup) {
					DefaultTreeTableModel model = (DefaultTreeTableModel) table.getTreeTableModel();
					model.removeNodeFromParent(moveNode);
					model.insertNodeInto(moveNode, targetNode, targetNode.getChildCount());
					
					moveNode.setParentElement(targetNode);
				}
			} catch (UnsupportedFlavorException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			super.drop(dtde);
		}
	}

	public class MyCustomTransferHandler extends TransferHandler {
		public MyCustomTransferHandler() {
			super();
		}

		protected Transferable createTransferable(JComponent c) {
			Transferable t = null;
			if (c instanceof AbstractTreeTableWithPopup) {
				t = new GenericTransferable(((AbstractTreeTableWithPopup) c).getPathForRow(getSelectedRow()));
			}
			return t;
		}
/*
		protected void exportDone(JComponent source, Transferable data, int action) {
			if (source instanceof AbstractTreeTableWithPopup) {
				AbstractTreeTableWithPopup tree = (AbstractTreeTableWithPopup) source;
				DefaultTreeTableModel model = (DefaultTreeTableModel) tree.getTreeTableModel();
				TreePath currentPath = tree.getPathForRow(getSelectedRow());
				if (currentPath != null) {
					AbstractDraggableElement targetNode = (AbstractDraggableElement) currentPath.getLastPathComponent();
					try {
						// TreePath[] movedPaths = (TreePath[]) data.getTransferData(DataFlavor.stringFlavor);
						TreePath movedPath = (TreePath) data.getTransferData(DataFlavor.stringFlavor);
						// for (int i = 0; i < movedPaths.length; i++) {
						AbstractDraggableElement moveNode = (AbstractDraggableElement) movedPath.getLastPathComponent();

						if (!moveNode.equals(targetNode)) {
							model.removeNodeFromParent(moveNode);
							model.insertNodeInto(moveNode, targetNode, targetNode.getChildCount());
						}
						// }
					} catch (UnsupportedFlavorException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
		*/

		/**
		 * add a number of given nodes
		 * 
		 * @param currentPath
		 *            the tree path currently selected
		 * @param model
		 *            tree model containing the nodes
		 * @param data
		 *            nodes to add
		 */
		private void addNodes(TreePath currentPath, DefaultTreeTableModel model, Transferable data) {
			AbstractDraggableElement targetNode = (AbstractDraggableElement) currentPath.getLastPathComponent();
			try {
				TreePath[] movedPaths = (TreePath[]) data.getTransferData(DataFlavor.stringFlavor);
				for (int i = 0; i < movedPaths.length; i++) {
					AbstractDraggableElement moveNode = (AbstractDraggableElement) movedPaths[i].getLastPathComponent();
					if (!moveNode.equals(targetNode)) {
						model.removeNodeFromParent(moveNode);
						model.insertNodeInto(moveNode, targetNode, targetNode.getChildCount());
					}
				}
			} catch (UnsupportedFlavorException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		/**
		 * insert a number of given nodes
		 * 
		 * @param tree
		 *            the tree showing the nodes
		 * @param model
		 *            the model containing the nodes
		 * @param data
		 *            the nodes to insert
		 *            private void insertNodes(JTree tree, DefaultTreeTableModel model, Transferable data) {
		 *            Point location = ((TreeDropTarget) tree.getDropTarget()).getMostRecentDragLocation();
		 *            TreePath path = tree.getClosestPathForLocation(location.x, location.y);
		 *            AbstractDraggableElement targetNode = (AbstractDraggableElement) path.getLastPathComponent();
		 *            AbstractDraggableElement parent = (AbstractDraggableElement) targetNode.getParent();
		 *            try {
		 *            TreePath[] movedPaths = (TreePath[]) data.getTransferData(DataFlavor.stringFlavor);
		 *            for (int i = 0; i < movedPaths.length; i++) {
		 *            AbstractDraggableElement moveNode = (AbstractDraggableElement) movedPaths[i].getLastPathComponent();
		 *            if (!moveNode.equals(targetNode)) {
		 *            model.removeNodeFromParent(moveNode);
		 *            model.insertNodeInto(moveNode, parent, model.getIndexOfChild(parent, targetNode));
		 *            }
		 *            }
		 *            } catch (UnsupportedFlavorException e) {
		 *            e.printStackTrace();
		 *            } catch (IOException e) {
		 *            e.printStackTrace();
		 *            }
		 *            }
		 */

		/**
		 * Returns the type of transfer actions supported by the source.
		 * This transfer handler supports moving of tree nodes so it returns MOVE.
		 * 
		 * @return TransferHandler.MOVE
		 */
		public int getSourceActions(JComponent c) {
			return TransferHandler.MOVE;
		}

		/**
		 * get a drag image from the currently dragged node (if any)
		 * 
		 * @param tree
		 *            the tree showing the node
		 * @return the image to draw during drag
		 */
		public BufferedImage getDragImage(JTree tree) {
			BufferedImage image = null;
			try {
				if (dragPath != null) {
					Rectangle pathBounds = tree.getPathBounds(dragPath);
					TreeCellRenderer r = tree.getCellRenderer();
					DefaultTreeTableModel m = (DefaultTreeTableModel) tree.getModel();
					boolean nIsLeaf = m.isLeaf(dragPath.getLastPathComponent());
					JComponent lbl = (JComponent) r.getTreeCellRendererComponent(tree, draggedNode, false,
							tree.isExpanded(dragPath), nIsLeaf, 0, false);
					lbl.setBounds(pathBounds);
					image = new BufferedImage(lbl.getWidth(), lbl.getHeight(),
							java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE);
					Graphics2D graphics = image.createGraphics();
					graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
					lbl.setOpaque(false);
					lbl.paint(graphics);
					graphics.dispose();
				}
			} catch (RuntimeException re) {
			}
			return image;
		}

		/** remember the path to the currently dragged node here (got from createTransferable) */
		private AbstractDraggableElement	draggedNode;
		/** remember the currently dragged node here (got from createTransferable) */
		private TreePath					dragPath;
	}
}
